unit uDZSchedule;

interface

uses
  uDZSysUtils, uDZLinkList, SysUtils, DateUtils, ExtCtrls;

type
  TDzTimeoutTask = class(TDzDblLinkListNode)
  private
    fTimeout: TDateTime;
  public
    procedure Execute; virtual; abstract;
    function Compare(other: TDzDblLinkListNode): TCompareResult; override;
    property Timeout: TDateTime read fTimeout write fTimeout;
  end;

  TDzSchedule = class
  private
    fTaskList: TDzDblLinkList;
    fTimer: TTimer;
    procedure UpdateInterval;
  protected
    procedure Timeoutd(Sender: TObject);
  public
    constructor Create;
    destructor Destroy; override;
    procedure AddTask(task: TDzTimeoutTask);
  end;


function Scheduler: TDzSchedule;
  
implementation

var
  _Scheduler: TDzSchedule;

function Scheduler: TDzSchedule;
begin
  if (_Scheduler = nil) then _Scheduler := TDzSchedule.Create;
  Result := _Scheduler;
end;

{ TDzTimeoutTask }

function TDzTimeoutTask.Compare(other: TDzDblLinkListNode): TCompareResult;
var
  diff: TDateTime;
begin
  diff := Self.Timeout - TDzTimeoutTask(other).Timeout;
  if (diff > 0) then Result := crGreater
  else if (diff = 0) then Result := crEqual
  else Result := crLess;
end;

{ TDzSchedule }

procedure TDzSchedule.AddTask(task: TDzTimeoutTask);
begin
  fTaskList.Push(task);
  UpdateInterval;
end;

constructor TDzSchedule.Create;
begin
  fTimer := TTimer.Create(nil);
  fTimer.Enabled := False;
  fTimer.OnTimer := Self.Timeoutd;
  fTaskList := TDzDblLinkList.Create(TDzTimeoutTask, lmStack, False);
end;

destructor TDzSchedule.Destroy;
begin
  CheckFreeAndNil(fTaskList);
  CheckFreeAndNil(fTimer);
  inherited;
end;

procedure TDzSchedule.Timeoutd(Sender: TObject);
var
  task: TDzTimeoutTask;
begin
  task := TDzTimeoutTask(fTaskList.Pop);
  fTimer.Enabled := False;
  try
    task.Execute;
  except

  end;
  task.Free;
  UpdateInterval;
end;

procedure TDzSchedule.UpdateInterval;
var
  nowTime: TDateTime;
  task: TDzTimeoutTask;
begin
  task := TDzTimeoutTask(fTaskList.ListHead);
  if (task = nil) then
  begin
    fTimer.Enabled := False;
    Exit;
  end;
  nowTime := Now;
  if (nowTime >= task.Timeout) then fTimer.Interval := 10
  else fTimer.Interval := MilliSecondsBetween(Now, task.Timeout);
  if (not fTimer.Enabled) then fTimer.Enabled := True;
end;

initialization

finalization
  if (_Scheduler <> nil) then _Scheduler.Free;

end.
